package com.hhsjdesign.dynamicui.asm.v1

import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.TypePath
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode

import java.util.jar.JarEntry
import java.util.jar.JarFile

/**
 * Scan all class in the package: com/alibaba/android/arouter/
 * find out all routers,interceptors and providers
 * @author billy.qi email: qiyilike@163.com
 * @since 17/3/20 11:48
 */
class ScanUtil {

    /**
     * scan jar file
     * @param jarFile All jar files that are compiled into apk
     * @param destFile dest file after this transform
     */
    static void scanJar(File jarFile, File destFile) {
        if (jarFile) {
            def file = new JarFile(jarFile)
            Enumeration enumeration = file.entries()
            //遍历jar文件中的元素
            while (enumeration.hasMoreElements()) {
                JarEntry jarEntry = (JarEntry) enumeration.nextElement()
                String entryName = jarEntry.getName()

                //如果是定义的文件名，执行asm的字节流操作,将扫描的类文件加入到指定的列表中
//                if (entryName.startsWith(ScanSetting.DYNAMICUI_CLASS_PACKAGE_NAME)) {
                if (entryName.endsWith(".class")
                        && !entryName.endsWith("R.class")
                        && !entryName.endsWith("R\$string.class")
                        && !entryName.endsWith("R\$id.class")
                        && !entryName.endsWith("R\$style.class")
                        && !entryName.endsWith("R\$styleable.class")
                        && !entryName.endsWith("R\$xml.class")
                        && !entryName.endsWith("R\$plurals.class")
                        && !entryName.endsWith("R\$layout.class")
                        && !entryName.endsWith("R\$mipmap.class")
                        && !entryName.endsWith("R\$interpolator.class")
                        && !entryName.endsWith("R\$integer.class")
                        && !entryName.endsWith("R\$dimen.class")
                        && !entryName.endsWith("R\$drawable.class")
                        && !entryName.endsWith("R\$bool.class")
                        && !entryName.endsWith("R\$color.class")
                        && !entryName.endsWith("R\$attr.class")
                        && !entryName.endsWith("R\$animator.class")
                        && !entryName.endsWith("R\$anim.class")
                        && !entryName.startsWith("org/intellij/lang/annotations")
                        && !entryName.startsWith("org/jetbrains/annotations")
                        && !entryName.startsWith("com/bumptech/glide")
                        && !entryName.startsWith("META-INF/")
                        && !entryName.startsWith("kotlin/")
                        && !entryName.startsWith("kotlinx/")
                        && !entryName.startsWith("androidx/")
                        && !entryName.startsWith("module-info")
                        && !entryName.startsWith("android/")
                        && !entryName.startsWith("com/google/android/")) {
                    println "jar节点下的类文件:${entryName}"
                    InputStream inputStream = file.getInputStream(jarEntry)
                    scanClass(inputStream)
                    inputStream.close()
                }
//                }

//                InputStream inputStream = file.getInputStream(jarEntry)
//                scanClass(inputStream)
//                inputStream.close()

                //目标类所在的文件名,即jar包名

                if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
                    //找到了目标文件
                    println "目标注入类文件:${entryName}"

                    // mark this jar file contains LogisticsCenter.class
                    // After the scan is complete, we will generate register code into this file
                    DynamicUiTransform.targetAsmClass = destFile
                }
            }
            file.close()
        }
    }

    static boolean shouldProcessPreDexJar(String path) {
        return !path.contains("androidx.") && !path.contains("com.android.support") && !path.contains("/android/m2repository")
    }

    //类名
    static boolean shouldProcessClass(String entryName) {
        return entryName != null
                && entryName.endsWith(".class")
//                && (entryName.startsWith(ScanSetting.DYNAMICUI_CLASS_PACKAGE_NAME) || entryName.equals(ScanSetting.GENERATE_TO_CLASS_NAME))
    }

    /**
     * 编译成才的 .class 文件
     * scan class file
     * @param class file
     */
    static void scanClassFile(File file) {
        println "directory节点下的类文件:${file.name}"
        InputStream inputStream = new FileInputStream(file)
        scanClass(inputStream)
        inputStream.close()
    }

    /**
     * 扫描类，只是为了拿到对应的注解信息
     * @param inputStream
     */
    static void scanClass(InputStream inputStream) {

        //首先读取class
        ClassReader cr = new ClassReader(inputStream)

        ClassNode cn = new ClassNode();//创建ClassNode,读取的信息会封装到这个类里面

        cr.accept(cn, ClassReader.EXPAND_FRAMES)

        List<AnnotationNode> annotations = cn.visibleAnnotations;//获取声明的所有注解
        StringBuilder builder = new StringBuilder()
        if (annotations != null) {//便利注解
            for (AnnotationNode an : annotations) {


                //获取注解的描述信息
                String anno = an.desc.replaceAll("/", ".");
                String annoName = anno.substring(1, anno.length() - 1);
                String annoType = null
                String annoValue = null
                String annoClassName = null



                annoType = getAnnoType(annoName)

                if (ScanSetting.ABusinessBean == annoName
                        || ScanSetting.ABusinessCaller == annoName
                        || ScanSetting.ABusinessViewHolder == annoName
                        || ScanSetting.AWidgetData == annoName
                        || ScanSetting.AWidgetItemData == annoName
                        || ScanSetting.AWidgetProperty == annoName
                        || ScanSetting.AWidgetType == annoName) {
                    builder.reverse()
                    for (Object o : an.values) {
                        builder.append("[").append(o.toString()).append("] ")
                    }
                    println "注解：${annoName} ${builder.toString()}"
                    //指定带注解才处理类
                    annoClassName = cn.name.replaceAll("/", ".");
                    //获取注解的属性名对应的值，（values是一个集合，它将注解的属性和属性值都放在了values中，通常奇数为值偶数为属性名）
                    if (an.values != null && an.values.size() > 1) {
                        annoValue = an.values.get(1).toString();
                        println("带注解值  类名:$annoClassName  ,注解值:$annoValue");
                        ScanSetting.annoValueSet.add(new ScanSetting.AnnoValue(annoType, annoValue, annoClassName))
                        //根据匹配的注解，将其封装给具体的业务使用
//                        ScanSetting.taskClazz.put(value, Class.forName(className));
                    } else {
                        println("不带注解值 类名:$annoClassName");
                    }
                }
            }
        }

        //给到writer
        ClassWriter cw = new ClassWriter(cr, 0)
        //用自定义的扫描
        ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM6, cw)
        cr.accept(cv, ClassReader.EXPAND_FRAMES)
        inputStream.close()
    }

    /**
     * 获取需要处理的注解，并根据类型分类
     * @param annoName
     * @return
     */
    static String getAnnoType(String annoName) {
        //组件 WIDGET = 0
        //业务数据 BUSINESS_BEAN = 1
        //组件数据对象 WIDGET_DATA = 2
        //组件属性类 WIDGET_PROPERTY = 3
        //组件视图处理类 VIEW_HOLDER = 4
        //组件数据获取类 BUSINESS_CALLER = 5
        //组件ITEM数据模型类 WIDGET_ITEM_DATA = 6
        String annoType = null
        switch (annoName) {
            case ScanSetting.AWidgetType:
                annoType = "WIDGET"
                break;
            case ScanSetting.ABusinessBean:
                annoType = "BUSINESS_BEAN"
                break;
            case ScanSetting.AWidgetData:
                annoType = "WIDGET_DATA"
                break;
            case ScanSetting.AWidgetProperty:
                annoType = "WIDGET_PROPERTY"
                break;
            case ScanSetting.ABusinessViewHolder:
                annoType = "VIEW_HOLDER"
                break;
            case ScanSetting.ABusinessCaller:
                annoType = "BUSINESS_CALLER"
                break;
            case ScanSetting.AWidgetItemData:
                annoType = "WIDGET_ITEM_DATA"
                break;
            case ScanSetting.ADynamicUI:
                break;
            case ScanSetting.AFromJSON:
                break;
            default:
                break
        }
        return annoType
    }

    //自定义访问者，继承类访问者
    static class ScanClassVisitor extends ClassVisitor {


        String tempClass;

        ScanClassVisitor(int api, ClassVisitor cv) {
            super(api, cv)
        }
        /**
         * 查找含有特定注解到，现在有7个注解
         * ABusinessBean
         * @param version
         * @param access 权限
         * @param name 类名
         * @param signature 签名
         * @param superName 父类名
         * @param interfaces 实现的接口名
         */
        void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces)
            StringBuilder interfaceArray = new StringBuilder();
            if (interfaces != null)
                for (String item : interfaces) {
                    interfaceArray.append(", ").append(item)
                }
            println "访问类：\n--类名:$name\n--泛型：$signature \n--父类:$superName\n--接口${interfaceArray.toString()}"
            DynamicUiTransform.registerList.forEach {
                if (name.contains("DynamicUIMap\$\$")) {
                    println "扫描到自动生成类:${name}"
                    it.classList.add(name)
                }
            }
        }

        @Override
        AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            DynamicUiTransform.registerList.forEach {
                if (descriptor.contains("/ABusinessBean")
                        || descriptor.contains("/ABusinessCaller")
                        || descriptor.contains("/ABusinessViewHolder")
                        || descriptor.contains("/ADynamicUI")
                        || descriptor.contains("/AFromJSON")
                        || descriptor.contains("/AWidgetData")
                        || descriptor.contains("/AWidgetItemData")
                        || descriptor.contains("/AWidgetProperty")
                        || descriptor.contains("/AWidgetType")) {
                    println("注解描述：${descriptor.substring(1).replaceAll("/", ".")}\n")
                }

                if (descriptor.contains("ABusinessBean")) {
                    if (it.targetClassName.get("ABusinessBean") == null) {
                        it.targetClassName.put("ABusinessBean", new HashSet<String>())
                    }
                    it.targetClassName.get("ABusinessBean").add(descriptor)
                } else if (descriptor.contains("ABusinessCaller")) {
                    if (it.targetClassName.get("ABusinessCaller") == null) {
                        it.targetClassName.put("ABusinessCaller", new HashSet<String>())
                    }
                    it.targetClassName.get("ABusinessCaller").add(descriptor)
                } else if (descriptor.contains("ABusinessViewHolder")) {
                    if (it.targetClassName.get("ABusinessViewHolder") == null) {
                        it.targetClassName.put("ABusinessViewHolder", new HashSet<String>())
                    }
                    it.targetClassName.get("ABusinessViewHolder").add(descriptor)
                } else if (descriptor.contains("ADynamicUI")) {
                    if (it.targetClassName.get("ADynamicUI") == null) {
                        it.targetClassName.put("ADynamicUI", new HashSet<String>())
                    }
                    it.targetClassName.get("ADynamicUI").add(descriptor)
                } else if (descriptor.contains("AFromJSON")) {
                    if (it.targetClassName.get("AFromJSON") == null) {
                        it.targetClassName.put("AFromJSON", new HashSet<String>())
                    }
                    it.targetClassName.get("AFromJSON").add(descriptor)
                } else if (descriptor.contains("AWidgetData")) {
                    if (it.targetClassName.get("AWidgetData") == null) {
                        it.targetClassName.put("AWidgetData", new HashSet<String>())
                    }
                    it.targetClassName.get("AWidgetData").add(descriptor)
                } else if (descriptor.contains("AWidgetItemData")) {
                    if (it.targetClassName.get("AWidgetItemData") == null) {
                        it.targetClassName.put("AWidgetItemData", new HashSet<String>())
                    }
                    it.targetClassName.get("AWidgetItemData").add(descriptor)
                } else if (descriptor.contains("AWidgetProperty")) {
                    if (it.targetClassName.get("AWidgetProperty") == null) {
                        it.targetClassName.put("AWidgetProperty", new HashSet<String>())
                    }
                    it.targetClassName.get("AWidgetProperty").add(descriptor)
                } else if (descriptor.contains("AWidgetType")) {
                    if (it.targetClassName.get("AWidgetType") == null) {
                        it.targetClassName.put("AWidgetType", new HashSet<String>())
                    }
                    it.targetClassName.get("AWidgetType").add(descriptor)
                }
            }
            return super.visitAnnotation(descriptor, visible)
        }

        @Override
        AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {
            return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)
        }
    }

}